
;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ 
;                            CPUTEST
;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ 
;    Locate potential hidden instructions on 80286 and later
;    CPUs.  Works by seeing if any "unused" opcodes do not trip
;    the bad opcode interrupt.  Known documented instructions
;    and known undocumented instructions are ignored.  Use
;    CPUTYPE to show and test validity of "known" undocumented 
;    instructions.
;
;    Use "CPUUNDOC +"  to only show valid instructions.
;
;    ------------------------- WARNING -------------------------
;
;    Since it is unknown what an undocumented instruction might
;    do, the routine attempts to protect against stack, memory,
;    and register alterations. It is conceivable an undocumented
;    instruction might do something that corrupts or even hangs
;    the system.  Should a hang occur, the display should show
;    the "undocumented" instruction bytes that caused the hang.
;
;    (c) Copyright 1994  Frank van Gilluwe  All Rights Reserved. 

include undocpc.inc


cseg    segment para public
        assume  cs:cseg, ds:cseg, ss:stacka

;ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ DATA AREA ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ

; ÄÄÄÄÄ general data

        db      'CPUUNDOC v1.00 '
        db      '(c) 1994 Frank van Gilluwe',0

cpuhead db      CR, LF, CR, LF
        db      '  ANALYSIS LOOKS FOR UNDOCUMENTED '
        db      'CPU INSTRUCTIONS       v1.00 (c) 1994 FVG'
        db      CR, LF
        db      '  ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ'
        db      'ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ'
        db      CR, LF, CR, LF, '$'

cputsting db    '  Testing instruction: '
cpuop     db                           '0Fh, '
cpuop2    db                                '00h'   
cpuop3    db                                   '       $'

opcodebad db    ' Instruction invalid.'
          db     CR, LF, '$'

opcodeok  db    ' Undocumented instruction is valid!'
          db    CR, LF, '$'

below286  db    '  No checks made.'
          db    '  CPU must be 286 or later for this test.'
          db    CR, LF, '$'

nonefound db    CR, LF
          db    '  No hidden undocumented instructions found '
          db    'on this CPU.', CR, LF, '$'

foundund  db    CR, LF
          db    '  One or mode hidden undocumented instructions found '
          db    'on this CPU!', CR, LF, '$'


cpu_val  db     0                  ; CPU value from CPUVALUE
                                   ;   0 = 8088/8086 or V20/V30
                                   ;   1 = 80186/80188
                                   ;   2 = 80286
                                   ;   3 = 80386
                                   ;   4 = 80486
                                   ;   5 = Pentium

cpu_info db     0                  ; flags from CPUVALUE
                                   ;   bit 0 = 1 if CPUID ok 
                                   ;       1 = 1 if V20/V30 CPU

cpu_prot db     0                  ; protected state
                                   ;   0 = no protected mode 
                                   ;   1 = real mode
                                   ;   2 = protected mode
                                   ;   3 = V86 mode

cpu_priv db     0                  ; privilege level 0-3

cmd_line db     0                  ; "-" = only show successes

old_int6_seg dw 0                  ; temp storage for old int 6
old_int6_off dw 0                  ;  vector (bad opcode)
badoff       dw 0                  ; temp return offset if bad 
                                   ;  opcode interrupt 6 called
savedss      dw 0                  ; temporary saved SS
savedsp      dw 0                  ; temporary saved SP

anyfound     db 0                  ; =1 if any instructions found

; The following bit maps exclude valid instruction 
;   combinations. A bit set indicates instruction combination 
;   to exclude. The first 16 words relate to the 2nd 
;   instruction byte 0 to FFh after the first byte 0Fh.  For 
;   example, the first word F700h indicates the 2nd bytes of 
;   instruction combinations 0, 1, 2, 3, 5, 6 and 7 are 
;   ignored. 

; The remaining four bytes are the exclusions for three byte 
;   instructions: 0Fh, 00h, xxh;  0Fh, 01h, xxh;  0Fh, BAh, 
;   xxh; and 0Fh, C7h, xxh.   In all four cases, the value xx, 
;   bits 5, 4, and 3 define unique possible instruction 
;   combinations. 


; 80286
exclude dw      0F700h,0F000h, 0000h, 0000h
        dw       0000h, 0000h, 0000h, 0000h
        dw       0000h, 0000h, 0000h, 0000h
        dw       0000h, 0000h, 0000h, 0000h
        db      0FCh, 0FAh, 00h, 00h

; 80386
        dw      0F700h,0F000h,0FA00h,0A000h
        dw       0000h, 0000h, 0000h, 0000h
        dw      0FFFFh,0FFFFh,0DFDDh, 3F3Fh
        dw       0000h, 0000h, 0000h, 0000h
        db      0FCh, 0FAh, 0Fh, 00h

; 80486

        dw      0F7C0h,0F000h,0FA00h,0A000h
        dw       0000h, 0000h, 0000h, 0000h
        dw      0FFFFh,0FFFFh,0DFFDh,0FF3Fh
        dw      0C0FFh, 0000h, 0000h, 0000h
        db      0FCh, 0FBh, 0Fh, 00h

; Pentium 

        dw      0F7C0h,0F000h,0FA00h,0E000h
        dw       0000h, 0000h, 0000h, 0000h
        dw      0FFFFh,0FFFFh,0FFFDh,0FF3Fh
        dw      0C1FFh, 0000h, 0000h, 0000h
        db      0FCh, 0FBh, 0Fh, 40h

; secondary test bytes

secondt db      0, 1, 0BAh, 0C7h

; third & fourth test pairs (8 pairs)  bytes picked so that:
;   1) 8 possible combinations of "reg" field tested
;   2) should the third byte be executed, nothing goes wrong
;   3) should the fourth byte be executed, nothing goes wrong

thirdt  db      02h, 0F8h
        db      0Ah, 0F8h
        db      12h, 0F8h
        db      1Ah, 0F8h 
        db      22h, 0F8h 
        db      2Ah, 0F8h 
        db      32h, 0F8h 
        db      3Ah, 0F8h 


;ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ CODE START ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ

.286

cputest proc    far

start:  
        cmp     byte ptr ds:[80h], 1  ; any option ?
        jbe     cf_skp1            ; jump if none
        mov     bl, ds:[82h]       ; get option on cmd line
        mov     cs:[cmd_line], bl  ; save option
cf_skp1:
        mov     ax, cs
        mov     ds, ax
        mov     es, ax
        OUTMSG  cpuhead            ; output header message

; get cpu type and cpumode, and save results

        call    cpuvalue           ; get CPU type 0 to 5 in al
        mov     [cpu_val], al      ; save
        mov     [cpu_info], ah     ; save flags
        call    cpumode            ; get current mode V86, real,
                                   ;   protected into ax
        mov     [cpu_prot], al     ; save type of protection
        mov     [cpu_priv], ah     ; save privledge level

; set up bad-opcode intercept        

        cmp     [cpu_val], 2       ; 286 or later ?
        jae     cf_skp2
        jmp     cf_exit1           ; exit if below 286
cf_skp2:
        push    es
        xor     ax, ax
        mov     es, ax
        cli                        ; disable interrupts
        mov     dx, es:[6*4]       ; get offset of int 6
        mov     cx, es:[6*4+2]     ; get segment
        mov     ax, offset cf_badop
        mov     es:[6*4], ax       ; set new vector
        mov     ax, cs
        mov     es:[6*4+2], ax
        sti                        ; enable interrupts
        pop     es
        mov     [old_int6_seg], cx ; save original vector
        mov     [old_int6_off], dx
        mov     ax, offset ct_bad  ; where to go when bad
        mov     [badoff], ax       ; save

        mov     al, [cpu_val]
        sub     al, 2
        mov     ah, 36             ; 36 bytes per table
        mul     ah                 ; point to CPU exclude table
        mov     si, offset exclude ; 
        add     si, ax             ; si = ptr to table to use

; --- Begin test cycles for 0Fh, xxh instruction combinations

        xor     dl, dl             ; 2nd instruction byte
        mov     bp, 16

cf_outer:
        mov     cx, 16
        mov     bx, [si]           ; get set of bits
cf_inner:
        rcl     bx, 1              ; bit to test in carry
        jc      cf_skp5            ; skip excluded instruction

cf_skp3:
        push    bx
        push    dx
        mov     al, dl
        call    hex2ascii          ; convert AL to ascii
        mov     word ptr [cpuop2], bx  ; insert in message
        cmp     [cmd_line], "+"    ; only show success ?
        je      cf_skp4
        OUTMSG  cputsting          ; output testing message
cf_skp4:
        pop     dx
        pop     bx

; Since we have no idea what other bytes the instruction 
;   might expect, we make the third byte F8h.  If a mm reg r/m 
;   field is expected, F8h indicates a register to register 
;   instruction using AX and DI.  If it is only a two byte 
;   instruction, F8h will be executed as a clear carry 
;   instruction.  The remaining bytes are filled with NOPs. 

        mov     ah, dl             ; insert instruction to test
        mov     al, 0F8h           ; possible mm reg r/m byte
        mov     dh, 90h            ; NOP fill byte

        call    ct_testit          ; go test the instruction &
                                   ;   display result
cf_skp5:
        inc     dl                 ; next instruction 
        loop    cf_inner

        add     si, 2              ; point to next exclude word
        dec     bp
        jnz     cf_outer

; --- Begin test cycles for 0Fh, {1, 2, BAh, C7h}, xxh 
;     instruction combinations.  It gets a little complicated.  
;     Since it might be possible for the instruction to be
;     two to four bytes long, the third and fourth bytes are 
;     carefully chosen to execute in a safe manor, should 
;     the instruction only be two or three bytes long.  The
;     third and fourth bytes are in the table THIRDT.

        mov     di, offset secondt ; pointer to 4 test bytes
        mov     bp, 4

cf_next_group:
        push    bp
        mov     bp, offset thirdt  ; table to 3rd & 4th bytes
        mov     cx, 8
        mov     bl, [si]           ; get exclusion bytes
cf_next1:
        rcl     bl, 1              ; move excusion into carry
        jc      cf_skp7

        push    bx
        push    dx
        mov     al, [di]
        call    hex2ascii          ; convert 2nd byte to ascii
        mov     word ptr [cpuop2], bx  ; insert in message
        mov     [cpuop3], ','      ; insert comma
        mov     al, ds:[bp]
        call    hex2ascii          ; convert 3rd byte to ascii
        mov     word ptr [cpuop3+2], bx  ; insert in message
        mov     byte ptr [cpuop3+4], 'h'
        cmp     [cmd_line], "+"    ; only show success ?
        je      cf_skp6
        OUTMSG  cputsting          ; output testing message
cf_skp6:
        pop     dx
        pop     bx

        mov     ah, [di]           ; insert instruction to test
        mov     al, ds:[bp]        ; get third byte
        mov     dh, ds:[bp+1]      ; get fourth byte

        call    ct_testit          ; go test the instruction &
                                   ;   display result
cf_skp7:
        add     bp, 2
        loop    cf_next1
        inc     si
        inc     di
        pop     bp
        dec     bp
        jnz     cf_next_group

; done with tests - restore interrupt 6 to original vector

        mov     cx, [old_int6_seg] ; get original vector
        mov     dx, [old_int6_off]
        push    es
        xor     ax, ax
        mov     es, ax
        cli                        ; disable interrupts
        mov     es:[6*4], dx       ; restore original int 6
        mov     es:[6*4+2], cx
        sti                        ; enable interrupts
        pop     es

        cmp     [anyfound], 1      ; were instructions found?
        je      cf_found           ; jump if so
        OUTMSG  nonefound          ; output none found message
        jmp     cf_exit            ; done!

cf_found:
        OUTMSG  foundund           ; output found message
        jmp     cf_exit            ; done!

cf_exit1:
        OUTMSG  below286           ; no test for CPU

cf_exit:
        mov     ah,4Ch
        int     21h                ; exit with al return code

;---------------------------------------------------------------
; if a bad opcode occurs (80286 or later) will come here
;   the saved BADOFF offset is used to return to the failed 
;   message routine 

cf_badop:
        push    ax
        push    bp
        mov     ax, cs:[badoff]
        mov     bp, sp
        mov     ss:[bp+4], ax      ; insert new return offset
        pop     bp
        pop     ax
        iret

cputest endp


;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
;    TEST IF INSTRUCTION IS VALID
;       All instructions to test are 0Fh, followed by three
;       bytes.  The balance of the "unknown" instruction is 
;       filled with NOPs.
;
;       Called with:    ah = 1st byte after 0Fh
;                       al = 2nd byte after 0Fh
;                       dh = 3rd byte after 0Fh
;                       [cmd_line] = "+" to ignore display of 
;                            failed conditions
;
;       Returns:        Message about success or failure

ct_testit proc  near
        push    di
        mov     [instbuf], ah      ; set instruction bytes
        mov     [instbuf+1], al
        mov     [instbuf+2], dh

        push    cx
        mov     cx, 13
        mov     di, offset instbuf+3
        mov     al, 90h            ; set rest of buffer to NOPs 
        cld
        rep     stosb
        pop     cx

        pusha
        push    ds
        push    es
        cli
        mov     [savedss], ss
        mov     [savedsp], sp
        push    ax                 ; push garbage onto stack
        push    ax                 ;  in case instruction pops
        push    ax                 ;  something from the stack
        push    ax

; The instruction to test 

        db      0Fh
instbuf db      16 dup (90h)       ; 16 nops

; only get here if instruction was not a bad opcode!

        cli
        mov     ss, cs:[savedss]   ; restore registers
        mov     sp, cs:[savedsp]
        pop     es
        pop     ds
        cmp     [cmd_line], "+"    ; only show success ?
        jne     ct_skp1            ; jump if not
        OUTMSG  cputsting          ; output testing message
ct_skp1:
        OUTMSG  opcodeok           ; success message
        mov     [anyfound], 1      ; flag at least one found
        popa
        sti                        ; enable interrupts
        jmp     ct_skp3

; int 6, bad opcode returns here if bad opcode detected

ct_bad:
        cli
        mov     ss, cs:[savedss]
        mov     sp, cs:[savedsp]
        pop     es
        pop     ds
        cmp     [cmd_line], "+"    ; ignore failures ?
        je      ct_skp2
        OUTMSG  opcodebad          ; failed message
ct_skp2:
        popa
        sti                        ; enable interrupts

ct_skp3:
        pop     di
        ret
ct_testit endp


;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
;    CPU IDENTIFICATION SUBROUTINE
;       Identify the CPU type, from 8088 to the Pentium.  Works 
;       even if the 386 or later CPU is in V86 mode.  Note that
;       interrupts are enabled at exit, even if they were 
;       disabled on entry.  If it is necessary to run this 
;       routine with interrupts disabled, just remove all CLI 
;       and STI instructions, so long as interrupts are 
;       always disabled before running.
;
;       Called with:    nothing
;
;       Returns:        al = CPU type
;                             0 if 8088/8086 or V20/V30
;                             1 if 80186/80188
;                             2 if 80286
;                             3 if 80386
;                             4 if 80486
;                             5 if Pentium
;                       ah =  bit 0 = 0 if CPUID unavailable
;                                     1 if CPUID ok
;                             bit 1 = 0 if not V20/V30
;                                     1 if NEC V20/V30
;
;       Regs used:      ax, bx (all)
;                       eax, ebx (386 or later)
;
;       Subs called:    hook_int6, restore_int6, bad_op_handler

.8086   ; all instructions 8088/8086 unless overridden later

cpuvalue proc    far
        push    cx
        push    dx
        push    ds
        push    es

; 8088/8086 test - Use rotate quirk - All later CPUs mask the CL
;   register with 0Fh, when shifting a byte by cl bits.  This 
;   test loads CL with a large value (20h) and shifts the AX
;   register right.  With the 8088, any bits in AX are shifted 
;   out, and becomes 0.  On all higher level processors, the
;   CL value of 20h is anded with 0Fh, before the shift.  This
;   means the effective number of shifts is 0, so AX is 
;   unaffected.

        mov     cl, 20h            ; load high CL value
        mov     ax, 1              ; load a non-zero value in AX
        shr     ax, cl             ; do the shift
        cmp     ax, 0              ; if zero, then 8088/86
        jne     up186              ; jump if not 8088/86

; V20/V30 test - It is now either a V20/V30 or a 8088.  I'll use
;   another undocumented trick to find out which.  On the 8088,
;   0Fh performs a POP CS.  On the V20/V30, it is the start of
;   a number of multi-byte instructions.  With the byte string
;   0Fh, 14h, C3h the CPU will perform the following:
;               8088/8086               V20/V30
;             pop     cs              set1   bl, cl  
;             adc     al, 0C3h

        xor     al, al             ; clear al and carry flag
        push    cs
        db      0Fh, 14h, 0C3h     ; instructions (see above)
        cmp     al, 0C3h           ; if al is C3h then 8088/8086
        jne     upV20
        mov     ax, 0              ; set 8088/8086 flag
        jmp     uP_Exit

upV20:
        pop     ax                 ; correct for lack of pop cs
        mov     ax, 200h           ; set V20/V30 flag
        jmp     uP_Exit

; 80186/80188 test - Check what is pushed onto the stack with a 
;   PUSH SP instruction.  The 80186 updates the stack pointer 
;   before the value of SP is pushed onto the stack.  With all
;   higher level processors, the current value of SP is pushed
;   onto the stack, and then the stack pointer is updated.
        
up186:  
        mov     bx, sp             ; save the current stack ptr
        push    sp                 ; do test
        pop     ax                 ; get the pushed value
        cmp     ax, bx             ; did SP change ?
        je      up286              ; if not, it's a 286+ 
        mov     ax, 1              ; set 80186 flag
        jmp     uP_Exit

; 80286 test A - We'll look at the top four bits of the EFLAGS 
;   register.  On a 286, these bits are always zero.  Later 
;   CPUs allow these bits to be changed.  During this test, 
;   We'll disable interrupts to ensure interrupts do not change 
;   the flags. 

up286: 
        cli                        ; disable interrupts
        pushf                      ; save the current flags

        pushf                      ; push flags onto stack 
        pop     ax                 ; now pop flags from stack
        or      ax, 0F000h         ; try and set bits 12-15 hi
        push    ax
        popf                       ; set new flags
        pushf
        pop     ax                 ; see if upper bits are 0

        popf                       ; restore flags to original
        sti                        ; enable interrupts
        test    ax, 0F000h         ; were any upper bits 1 ?
        jnz     up386              ; if so, not a 286

; 80286 test B - If the system was in V86 mode, (386 or higher) 
;   the POPF instruction causes a protection fault, and the 
;   protected mode software must emulate the action of POPF. If 
;   the protected mode software screws up, as occurs with a 
;   rarely encountered bug in Windows 3.1 enhanced mode, the 
;   prior test may look like a 286, but it's really a higher 
;   level processor. We'll check if the protected mode bit is 
;   on.  If not, it's guaranteed to be a 286.

.286P                              ; allow a 286 instruction
        smsw    ax                 ; get machine status word
        test    ax, 1              ; in protected mode ?
        jz      is286              ; jump if not (must be 286)

; 80286 test C - It's very likely a 386 or greater, but it is 
;   not guaranteed yet.  There is a small possibility the system 
;   could be in 286 protected mode so we'll do one last test. We 
;   will try out a 386 unique instruction, after vectoring the 
;   bad-opcode interrupt vector (int 6) to ourselves.  

        call    hook_int6          ; do it!
        mov     [badoff], offset upbad_op  ; where to go if bad
.386
        xchg    eax, eax           ; 32 bit nop (bad on 286)
        
        call    restore_int6       ; restore vector
        jmp     up386              ; only gets here if 386 
                                   ;  or greater!

; Interrupt vector 6 (bad opcode) comes here if system is a 
;   80286 (assuming the 286 protected mode interrupt 6 handler 
;   will execute the bad-opcode interrupt). 

upbad_op:
        call    restore_int6
is286:
        mov     ax, 2              ; set 80286 flag
        jmp     uP_Exit

; 80386 test - Bit 18 in EFLAGS is not settable on a 386, but is
;   changeable on the 486 and later CPUs.  Bit 18 is used to 
;   flag alignment faults. During this test, we'll disable 
;   interrupts to ensure no interrupt will change any flags.

.386                               ; allow 386 instructions

up386:
        cli                        ; disable interrupts
        pushfd                     ; push flags to look at
        pop     eax                ; get eflags
        mov     ebx, eax           ; save for later
        xor     eax, 40000h        ; toggle bit 18
        push    eax                                    
        popfd                      ; load modified eflags to CPU
        pushfd                     ; push eflags to look at
        pop     eax                ; get current eflags
        push    ebx                ; push original onto stack
        popfd                      ; restore original flags
        sti                        ; enable interrupts
        xor     eax, ebx           ; check if bit changed 
        jnz     up486              ; changed, so 486 or later
        mov     ax, 3              ; set 80386 flag
        jmp     uP_Exit

; 80486 test - Bit 21 in EFLAGS is not settable on a 486, but is
;   changeable on the Pentium CPU.  If bit 21 is changeable, it 
;   indicates the CPU supports the CPUID instruction.  It's 
;   amazing it's only taken 10 years to implement the CPUID 
;   instruction, which should have been included from the start!  
;   During this test, we'll disable interrupts to ensure no 
;   interrupt will change any flags. 

up486:
        cli                        ; disable interrupts
        pushfd                     ; push flags to look at
        pop     eax                ; get eflags
        mov     ebx, eax           ; save for later
        xor     eax, 200000h       ; toggle bit 21
        push    eax                                    
        popfd                      ; load modified eflags to CPU
        pushfd                     ; push eflags to look at
        pop     eax                ; get current eflags
        push    ebx                ; push original onto stack
        popfd                      ; restore original flags
        sti                        ; enable interrupts
        xor     eax, ebx           ; check if bit changed 
        jnz     upPentium          ; changed, it's a Pentium
        mov     ax, 4              ; set 80486 flag
        jmp     uP_Exit

; Pentium - It's possible the CPUID instruction may appear on 
;   other CPU chips, so run the CPUID instruction to see what 
;   CPU type it indicates.  The CPUID returns a family number 
;   0 to 5 for the processor type.  As of this date, only the 
;   Pentium supports the CPUID instruction and it is assigned 
;   type 5.

upPentium:
        push    ecx                ; CPUID changes eax to edx
        push    edx
        mov     eax, 1             ; get family info function
        CPUID                      ; macro for CPUID instruction
        and     eax, 0F00h         ; find family info
        shr     eax, 8             ; move to al
        mov     ah, 1              ; set flag that CPUID ok
        pop     edx
        pop     ecx
       
up_Exit:
        pop     es
        pop     ds
        pop     dx
        pop     cx
        ret
cpuvalue endp
.8086                              ; return to 8086 instructions


;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
;    HOOK INTERRUPT 6
;       Save the old interrupt 6 vector and replace it with  
;       a new vector to the bad_op_handler.  Vectors are handled 
;       directly without using DOS.
;
;       Called with:    nothing
;                      
;       Returns:        vector hooked
;                       old vector stored at
;                         ds:[old_int6_seg]
;                         ds:[old_int6_off]
;
;       Regs used:      none

hook_int6 proc    near
        push    ax
        push    cx
        push    es
        xor     ax, ax
        mov     es, ax
        cli                        ; disable interrupts
        mov     ax, es:[6*4]       ; get offset of int 6
        mov     cx, es:[6*4+2]     ; get segment
        mov     es:[6*4], offset bad_op_handler
        mov     word ptr es:[6*4+2], seg bad_op_handler
        sti                        ; enable interrupts
        mov     [old_int6_seg], cx ; save original vector
        mov     [old_int6_off], ax
        pop     es
        pop     cx
        pop     ax
        ret
hook_int6 endp


;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
;    RESTORE INTERRUPT 6
;       Restore the previously saved old interrupt 6 vector.
;       Vectors handled directly without using DOS.
;
;       Called with:    old vector stored at
;                         ds:[old_int6_seg]
;                         ds:[old_int6_off]
;                      
;       Returns:        vector restored
;
;       Regs used:      none

restore_int6 proc    near
        push    ax
        push    cx
        push    dx
        mov     cx, [old_int6_seg] ; get original vector
        mov     dx, [old_int6_off]
        push    es
        xor     ax, ax
        mov     es, ax
        cli                        ; disable interrupts
        mov     es:[6*4], dx       ; restore original int 6
        mov     es:[6*4+2], cx
        sti                        ; enable interrupts
        pop     es
        pop     dx
        pop     cx
        pop     ax
        ret
restore_int6 endp


;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
;    BAD OFFSET INTERRUPT HANDLER
;       If a bad opcode occurs (80286 or later) will come here.
;       The saved BADOFF offset is used to goto the routine
;       previously stored in BADOFF.
;
;       In a few cases, it is also used for double faults. A few
;       instructions (RDMSR & WRMSR) can issue a double fault if
;       not supported, so well come here as well.
;
;       Called with:    cs:[badoff] previously set
;
;       Returns:        returns to address stored in badoff


bad_op_handler proc far
        push    ax
        push    bp
        mov     ax, cs:[badoff]
        mov     bp, sp
        mov     ss:[bp+4], ax      ; insert new return offset
        pop     bp
        pop     ax
        iret
bad_op_handler endp


;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
;    CPU MODE
;       Check if the 286 or later CPU is in real, protected or
;       V86 mode.  It is assumed that if the 80386 or later
;       processor is in protected mode, we must be in V86 mode.
;
;       Call with:      ds:[cpu_val] set
;
;       Returns:        al = 0 protected mode not supported 
;                            1 if real mode 
;                            2 if protected mode
;                            3 if V86 mode
;                       ah = privilege level 0 to 3 
;
;       Regs used:      ax

.386P                              ; allow 286/386 instructions

cpumode proc    near
        push    cx
        xor     cx, cx             ; assume no protected mode
        cmp     [cpu_val], 2       ; 286 CPU or later ?
        jb      cpum_Exit          ; jump if not
        mov     cx, 1              ; assume real mode flag
        smsw    ax                 ; get machine status word
        test    ax, 1              ; in protected mode ?
        jz      cpum_Exit          ; jump if not (real mode)
        mov     cl, 2              ; protected mode
        pushf
        pop     ax                 ; get flags
        and     ax, 3000h          ; get I/O privilege level
        shl     ax,12
        mov     ch, al             ; save privilege
        cmp     [cpu_val], 2       ; if 286, then protected
        je      cpum_Exit          ; jump if so

; On 386 or later, we have to assume V86 mode.  Note that the 
;  next four lines of code (commented out) might seem the 
;  correct way to detect V86 mode.  It will not work, since the 
;  PUSHFD instruction clears the VM bit before placing it on the 
;  stack.  This is undocumented on the 386 and 486, but 
;  documented on the Pentium. 

;        pushfd
;        pop     eax                ; get extended flags
;        test    eax, 20000h        ; V86 mode ?
;        jz      cpum_out_mode      ; jump if not

        mov     cl, 3              ; return V86 mode

cpum_Exit:
        mov     ax, cx             ; return status
        pop     cx
        ret
cpumode endp
.8086


;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
;   HEX2ASCII
;       Convert the hex number in al into two ascii characters
;
;       Called with:    al = input hex number
;
;       Returns         bx = converted digits in ascii
;
;       Regs Used:      al, bx

hex2ascii       proc    near
        mov     bl, al
        and     al, 0fh
        add     al, 90h
        daa
        adc     al, 40h
        daa
        mov     bh, al

        mov     al, bl             ; upper nibble
        shr     al, 1
        shr     al, 1
        shr     al, 1
        shr     al, 1
        and     al, 0fh
        add     al, 90h
        daa
        adc     al, 40h
        daa
        mov     bl, al             ; bx has two ascii bytes
        ret
hex2ascii       endp



cseg    ends

;================================================== stack ======

stacka  segment para stack

        db      192 dup (0)

stacka  ends

        end     start



